코드 원본 및 참고 자료
0. 들어가기 전에 알아야될 용어
- Matrix factorization
- 기본적으로 행렬인수 분해 알고리즘은 더 낮은 차원으로 사용자 및 항목 속성을 나타내는 잠재 요인을 찾으려고 함
- 학습 결과는 가능한 관찰된 등급에 가깝게 분해 결과를 수렴하도록 개발
- 또한 오버피팅 문제를 피하기 위해 학습 과정을 정규화. 예를 들어, 이러한 행렬 인수 분해 알고리즘의 기본 형태는 다음과 같음
1. SAR Single Node on MovieLens (Python, CPU)
- 노트북 예제에서는 Python 단일 노드 구현을 사용하여 Smart Adaptive Recommendations (SAR) 알고리즘의 각 단계를 수행합니다.
- SAR은 사용자 트랜잭션 기록을 기반에 개인화된 추천시스템을 위한 빠르고 확장 가능한 적응형 알고리즘입니다. 아이템 간의 유사성을 이해하고 사용자가 기존 친화력을 가진 아이템과 유사한 아이템을 추천함으로써 강화됩니다.
2. SAR algorithm
- 다음 그림은 SAR의 상위 레벨 아키텍처를 나타냅니다. 높은 수준에서 두 개의 중간 행렬이 만들어지고 추천점수 집합을 생성하는 데 사용됩니다.
- 항목 유사성 행렬 S은 항목 - 항목 관계를 추정합니다. 친화 행렬 A는 사용자 - 항목 관계를 추정합니다.
- 추천 점수는 행렬 곱셈 A X S를 계산하여 생성됩니다.
- 선택 단계 (예 : ‘time decay’ 및 ‘remove seen items’)는 아래에 자세히 설명되어 있습니다.
2.1 Compute item co-occurrence and item similarity
- SAR은 항목 대 항목 동시 발생 (co-occurrence) 데이터를 기반으로 유사성을 정의합니다. 동시 발생 (co-occurrence)은 주어진 사용자에 대해 두 항목이 함께 나타나는 횟수로 정의됩니다.
- 모든 항목의 동시 발생을 m X m matrix C로 나타낼 수 있습니다. c_i, j는 항목 i이 항목 j 및 m는 총 항목 수입니다.
- co-occurence matric C에는 다음과 같은 속성이 있습니다.
- It is symmetric
- It is nonnegative
- The occurrences are at least as large as the co-occurrences. I.e., the largest element for each row (and column) is on the main diagonal: (발생은 적어도 동시 발생만큼 큽니다. 즉, 각 행 (및 열)에 대한 가장 큰 요소는 주 대각선)
-
co-occurrence 행렬이 생기면 주어진 유사도에 따라 co-occurrence을 재조정하여 항목 유사도 행렬 S를 얻을 수 있습니다. 메트릭에 대한 옵션에는
Jaccard
,lift
및counts
(재조정 없음)가 포함됩니다. -
Jaccard
: -
lift
: -
counts
: - 일반적으로 ‘카운트’를 유사성 측정 항목으로 사용하면 예측 가능성이 좋아지므로 가장 인기있는 항목이 대부분 추천될 것입니다.
- 대조적으로 ‘리프트’는 검색 가능성 / 검색 가능성을 선호합니다. 전반적으로 인기는 적지만 소량의 사용자가 선호하는 항목은 추천할 가능성이 큽니다. ‘자카드 (Jaccard)’는 둘 사이의 절충안이다.
2.2 Compute user affinity scores
-
SAR의 affinity 행렬은 각 개별 사용자와 사용자가 이미 상호 작용한 항목 간의 관계의 강도를 포착합니다. SAR은 사용자의 친화력에 영향을 줄 수 있는 두 가지 요소를 통합합니다.
- 다른 이벤트의 다른 가중치 부여를 통해 사용자-항목 상호 작용의 유형 에 대한 정보를 고려할 수 있습니다
- (예 : 사용자가 항목을 본 이벤트보다 사용자가 특정 항목을 더 많이 평가한 이벤트의 무게를 측정할 수 있음)
- 사용자-항목 이벤트가 발생한 에 대한 정보를 고려할 수 있습니다 (예 : 먼 과거에 발생한 이벤트의 가치를 할인 할 수 있음)
- 이러한 요소를 공식화 하면 사용자 항목 유사성에 대한 표현이 생깁니다.
- 사용자 i와 항목 j에 대한 친화력 는 사용자 i와 항목 j가 관련된 모든 k 이벤트의 가중 합계입니다.
- w_k는 특정 이벤트의 가중치를 나타내며 2항의 거듭 제곱은 일시적으로 할인된 이벤트를 반영합니다. t단위가 반감기로 작용하게 됩니다. t_0보다 먼저 T 단위가 t_0보다 먼저 발생하게 됩니다.
- 모든 n 사용자 및 m 항목에 대해 이 계산을 반복하면 n X m matrix A 가 됩니다.
- 위의 식의 단순화는 모든 가중치를 1(이벤트 유형을 효과적으로 무시)과 같게 설정하거나 반감기 매개변수 T를 무한대 (트랜잭션 시간 무시)로 설정하여 얻을 수 있습니다.
2.3 Remove seen item
- 선택에 따라 이미 훈련 세트에 표시된 항목을 삭제합니다. 즉, 이전에 사용자가 구매한 항목을 다시 추천하지 않는 것입니다.
2.4 Top-k item calculation
- 다음 선호도 행렬 A에 유사도 행렬 S을 곱하여 사용자 집합에 대한 맞춤형 추천을 얻을 수 있습니다. 결과는 추천점수 매트릭스입니다.
- 각 행은 사용자에 해당하고 각 열은 항목에 해당하며 각 항목은 사용자 / 항목 쌍에 해당합니다. 높은 점수는 더 강하게 추천되는 항목에 해당합니다.
- 추천 작업의 복잡성은 데이터 크기에 따라 다릅니다. SAR 알고리즘 자체는 복잡합니다.
- 따라서 단일 노드 구현은 대규모 데이터 세트를 확장 가능한 방식으로 처리하지 않아야 합니다. 알고리즘을 사용할 때마다 충분히 큰 메모리로 실행하는 것이 좋습니다.
3. SAR single-node implementation
-
이 노트북에 설명 된 SAR 구현은 파이썬에서 주로 numpy, pandas 및 scipy와 같은 Python 패키지로 개발되었으며 대부분 데이터 분석 / 기계학습 작업에서 사용됩니다. 구현 세부 정보는 아래에서 찾을 수 있습니다.
3.1 SAR single-node based movie recommender
# set the environment path to find Recommenders
import sys
sys.path.append("../../")
import itertools
import logging
import os
import numpy as np
import pandas as pd
import papermill as pm
from reco_utils.dataset import movielens
from reco_utils.dataset.python_splitters import python_random_split
from reco_utils.evaluation.python_evaluation import map_at_k, ndcg_at_k, precision_at_k, recall_at_k
from reco_utils.recommender.sar.sar_singlenode import SARSingleNode
print("System version: {}".format(sys.version))
print("Pandas version: {}".format(pd.__version__))
System version: 3.5.2 (default, Nov 12 2018, 13:43:14)
[GCC 5.4.0 20160609]
Pandas version: 0.23.4
# top k items to recommend
TOP_K = 10
# Select Movielens data size: 100k, 1m, 10m, or 20m
MOVIELENS_DATA_SIZE = '20m'
3.2 Load Data
-
SAR은 다음 스키마와의 상호 작용에 사용하기 위한 것입니다.
<사용자 ID>, <아이템 ID>, <시간>
- 각 행은 사용자와 항목간의 단일 상호 작용을 나타냅니다. 상호 작용은 전자상거래 웹사이트에서 사용자가 항목을 클릭하여 보고 장바구니에 추가하거나 추천 링크를 따라 클릭하는 등 다양한 유형의 이벤트일 수 있습니다.
- MovieLens 데이터 세트는 영화에 등급을 제공하는 사용자의 형식이 잘 지정된 상호 작용입니다 (영화 등급은 이벤트 가중치로 사용됩니다). 나머지 예제에서는 이 등급을 사용합니다.
data = movielens.load_pandas_df(
size=MOVIELENS_DATA_SIZE,
header=['UserId', 'MovieId', 'Rating', 'Timestamp'],
title_col='Title'
)
# Convert the float precision to 32-bit in order to reduce memory consumption
data.loc[:, 'Rating'] = data['Rating'].astype(np.float64)
data.head()
UserId | MovieId | Rating | Timestamp | Title | |
---|---|---|---|---|---|
0 | 1 | 2 | 3.5 | 1112486027 | Jumanji (1995) |
1 | 5 | 2 | 3.0 | 851527569 | Jumanji (1995) |
2 | 13 | 2 | 3.0 | 849082742 | Jumanji (1995) |
3 | 29 | 2 | 3.0 | 835562174 | Jumanji (1995) |
4 | 34 | 2 | 3.0 | 846509384 | Jumanji (1995) |
data.shape()
3.3 Split the data using the python random splitter provided in utilities:
We utilize the provided python_random_split
function to split into train
and test
datasets randomly at a 75/25 ratio.
train, test = python_random_split(data, 0.75)
header = {
"col_user": "UserId",
"col_item": "MovieId",
"col_rating": "Rating",
"col_timestamp": "Timestamp",
"col_prediction": "Prediction",
}
In this case, for the illustration purpose, the following parameter values are used:
Parameter | Value | Description |
---|---|---|
similarity_type |
jaccard |
Method used to calculate item similarity. |
time_decay_coefficient |
30 | Period in days (term of $T$ shown in the formula of Section 2.2.2) |
time_now |
None |
Time decay reference. |
timedecay_formula |
True |
Whether time decay formula is used. |
# set log level to INFO
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(levelname)-8s %(message)s')
model = SARSingleNode(
similarity_type="jaccard",
time_decay_coefficient=30,
time_now=None,
timedecay_formula=True,
**header
)
30 * 24 * 60 * 60
2592000
model.fit(train)
2019-05-16 07:02:37,345 INFO Collecting user affinity matrix
2019-05-16 07:02:37,347 INFO Calculating time-decayed affinities
2019-05-16 07:02:37,372 INFO Creating index columns
2019-05-16 07:02:37,381 INFO Building user affinity sparse matrix
2019-05-16 07:02:37,385 INFO Calculating item co-occurrence
2019-05-16 07:02:37,518 INFO Calculating item similarity
2019-05-16 07:02:37,519 INFO Calculating jaccard
2019-05-16 07:02:37,574 INFO Done training
top_k = model.recommend_k_items(test, remove_seen=True)
2019-05-16 07:08:35,951 INFO Calculating recommendation scores
2019-05-16 07:08:36,020 INFO Removing seen items
2019-05-16 07:08:36,025 INFO Getting top K
top_k.shape
(9430, 3)
The final output from the recommend_k_items
method generates recommendation scores for each user-item pair, which are shown as follows.
top_k_with_titles = (top_k.join(data[['MovieId', 'Title']].drop_duplicates().set_index('MovieId'),
on='MovieId',
how='inner').sort_values(by=['UserId', 'Prediction'], ascending=False))
display(top_k_with_titles.head(10))
MovieId | Prediction | UserId | Title | |
---|---|---|---|---|
4678 | 385 | 22.063621 | 943 | True Lies (1994) |
4674 | 79 | 20.945250 | 943 | Fugitive, The (1993) |
4675 | 69 | 20.665495 | 943 | Forrest Gump (1994) |
4671 | 82 | 20.615830 | 943 | Jurassic Park (1993) |
4676 | 202 | 20.333979 | 943 | Groundhog Day (1993) |
4672 | 550 | 20.223571 | 943 | Die Hard: With a Vengeance (1995) |
4677 | 265 | 20.060790 | 943 | Hunt for Red October, The (1990) |
4679 | 183 | 19.947005 | 943 | Alien (1979) |
4673 | 403 | 19.897582 | 943 | Batman (1989) |
4670 | 168 | 19.895860 | 943 | Monty Python and the Holy Grail (1974) |
data[['MovieId', 'Title']].drop_duplicates()
MovieId | Title | |
---|---|---|
0 | 242 | Kolya (1996) |
117 | 302 | L.A. Confidential (1997) |
414 | 377 | Heavyweights (1994) |
... | ... | ... |
99996 | 1640 | Eighth Day, The (1996) |
99997 | 1637 | Girls Town (1996) |
99998 | 1630 | Silence of the Palace, The (Saimt el Qusur) (1... |
99999 | 1641 | Dadetown (1995) |
1682 rows × 2 columns
3.4 Evaluate the results
- 항목 유사도 행렬 S와 사용자 선호도 행렬 A가 행렬 곱으로생성된 추천 점수는 movielens 데이터 세트의 원래 명시적 등급과 동일한 축을 가져야 합니다.
- 즉, SAR 알고리즘은 사용자 - 항목 쌍에 대한 명시적 등급을 “예측하는 것”이 아니라 관련되는 항목을 사용자에게 “추천하는 작업”을 의미합니다.
- RMSE와 같은 평가 지표보다 precision@k, recall@k 등과 같은 순위측정 기준은 SAR 알고리즘을 평가하는 데 더 적합합니다.
- 다음은
reco_utils
에 제공된 평가 함수를 사용하여 SAR 모델을 평가하는 방법을 보여줍니다.
# all ranking metrics have the same arguments
args = [test, top_k]
kwargs = dict(col_user='UserId',
col_item='MovieId',
col_rating='Rating',
col_prediction='Prediction',
relevancy_method='top_k',
k=TOP_K)
eval_map = map_at_k(*args, **kwargs)
eval_ndcg = ndcg_at_k(*args, **kwargs)
eval_precision = precision_at_k(*args, **kwargs)
eval_recall = recall_at_k(*args, **kwargs)
test.shape
(25000, 5)
test.head()
UserId | MovieId | Rating | Timestamp | Title | |
---|---|---|---|---|---|
75721 | 498 | 693 | 3.0 | 881957625 | Casino (1995) |
80184 | 642 | 542 | 5.0 | 885606609 | Pocahontas (1995) |
19864 | 58 | 135 | 4.0 | 884305150 | 2001: A Space Odyssey (1968) |
76699 | 495 | 674 | 3.0 | 888635995 | Cat People (1982) |
92991 | 618 | 735 | 3.0 | 891308571 | Philadelphia (1993) |
top_k.shape
(9430, 3)
top_k.head()
MovieId | Prediction | UserId | |
---|---|---|---|
0 | 69 | 3.160962 | 498 |
1 | 196 | 3.170867 | 498 |
2 | 132 | 3.176901 | 498 |
3 | 234 | 3.185214 | 498 |
4 | 96 | 3.193433 | 498 |
print({model.model_str},{TOP_K},{eval_map},{eval_ndcg},{eval_precision},{eval_recall}, sep='\n')
{'sar_ref'}
{10}
{0.10350057001415401}
{0.3681048446660098}
{0.3176033934252386}
{0.16970898480940927}
댓글남기기